1. DBUtils的介绍

  • DBUtils是Python的一个用于实现数据库连接池的模块

  • 如果没有连接池,使用pymysql来连接数据库时,单线程应用不会导致数据的混乱,但如果涉及到多线程应用就会导致数据的混乱,那么此时就需要加锁,但是一旦加锁那么连接势必就会排队等待,当请求比较多时,性能就会降低了

  • 注意: 

    • 在以后的日常开发中如果使用到了 pymysql 就一定要使用 DBUtils 数据库连接池

    • 必须使用模块的单例模式创建数据库连接池

2. DBUtils的安装

pip3 install DBUtils -i https://pypi.douban.com/simple # 使用豆瓣的镜像

3. DBUtils的使用

  • 模式一 -> 不推荐使用

    • 为每一个线程创建一个数据库连接,当前线程中如果进行多次数据库操作时,永远使用同一个连接

      • 通俗理解: 有多少个线程就创建多少个连接,如果当前线程有多次数据库操作,那么永远使用通过自身线程所创建的连接进行操作

      • 不推荐使用: 因为如果有 1000 个线程就会创建 1000 个连接

# db_pool.py

# 使用模块的单例模式创建数据库连接池

import pymysql
from DBUtils.PersistentDB import PersistentDB

# 创建数据库连接池
POOL = PersistentDB(
    creator=pymysql,  # 使用链接数据库的模块
    maxusage=None,  # 一个链接最多被重复使用的次数,None表示无限制
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,  # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
    closeable=False,  # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
    threadlocal=None,  # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置
    host='127.0.0.1',  # 数据库IP
    port=3306,  # 数据库端口号
    user='root',  # 数据库用户名
    password='',  # 数据库密码
    database='school',  # 数据库名称
    charset='utf8'  # 数据库的编码格式
)

# use_db_pool.py

import pymysql
import threading
from db_pool import POOL


def task():
    conn = POOL.connection()  # 获取连接
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)  # 游标: 操作数据
    cursor.execute('select * from student')  # 执行SQL语句
    result = cursor.fetchall()  # 获取查询到的所有数据
    cursor.close()  # 关闭游标
    conn.close()  # 将连接放回连接池中 -> 这里的 .close 不是关闭连接,而是将连接返回连接池中
    print(result)  # [{'id': 1, 'name': 'Kevin', 'age': 18}, {'id': 2, 'name': 'Yeung', 'age': 22}]


# 开启 10 个线程
for i in range(10):
    t = threading.Thread(target=task)
    t.start()

  • 模式一 -> 推荐使用

    • 创建一批连接到连接池,供所有线程共享使用

      •  注意: 连接池中的连接个数是你所指定的,当连接池中的连接已被线程取完,此时如果有线程来了再想取连接时只能等待其他线程用完连接后返回连接池中

# db_pool.py

# 使用模块的单例模式创建数据库连接池

import pymysql
from DBUtils.PooledDB import PooledDB

# 创建数据库连接池
POOL = PooledDB(
    creator=pymysql,  # 使用连接数据库的模块
    maxconnections=6,  # 连接池允许的最大连接数,0 和 None 表示不限制连接数
    mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0 表示不创建 -> 即: 在初始化连接池的时候,不会开六个连接,而是先开两个,当连接不够用的时候再开6个
    maxcached=5,  # 链接池中最多闲置的链接,0 和 None不限制 -> 即: 当连接闲置(不用)的时候,保留几个连接,其余的全部关闭掉
    maxshared=3,  # 链接池中最多共享的链接数量,0 和 None表示全部共享 -> PS: 无用,因为 pymysql 和 MySQLdb 等模块的 threadsafety 都为 1,所有值无论设置为多少,_maxcached 永远为 0,所以永远是所有链接都共享
    blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待,True: 等待,False: 不等待然后报错
    maxusage=None,  # 一个链接最多被重复使用的次数,None: 表示无限制 -> 即: 一个连接在使用num次之后就要被重新创建
    setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
    ping=0,  # ping MySQL服务端,检查是否服务可用 -> 即: 在每次执行数据库操作前先检测一下连接是否断开
             # 值:
             # 0 = None = never -> 不进行检测 -> 常用
             # 1 = default = whenever it is requested -> 往mysql发送请求的时候进行检测
             # 2 = when a cursor is created -> 创建游标的时候进行检测
             # 4 = when a query is executed -> 执行sql语句的时候进行检测 -> 常用
             # 7 = always -> 一旦有数据库操作就进行检测
    host='127.0.0.1',  # 数据库IP
    port=3306,  # 数据库端口号
    user='root',  # 数据库用户名
    password='',  # 数据库密码
    database='school',  # 数据库名称
    charset='utf8'  # 数据库的编码格式
)

# use_db_pool.py

import pymysql
import threading
from db_pool import POOL


def task():
    conn = POOL.connection()  # 获取连接
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)  # 游标: 操作数据
    cursor.execute('select * from student')  # 执行SQL语句
    result = cursor.fetchall()  # 获取查询到的所有数据
    cursor.close()  # 关闭游标
    conn.close()  # 将连接放回连接池中 -> 这里的 .close 不是关闭连接,而是将连接返回连接池中
    print(result)  # [{'id': 1, 'name': 'Kevin', 'age': 18}, {'id': 2, 'name': 'Yeung', 'age': 22}]


# 开启 10 个线程
for i in range(10):
    t = threading.Thread(target=task)
    t.start()

4. DBUtils的例子

dbutils_demo.rar

  • 在配置文件中添加数据库连接池

# settings.py

import pymysql
from DBUtils.PooledDB import PooledDB

# 创建数据库连接池
POOL = PooledDB(
    creator=pymysql,
    maxconnections=6,
    mincached=2,
    maxcached=5,
    maxshared=3,
    blocking=True,
    maxusage=None,
    setsession=[],
    ping=0,
    host='127.0.0.1',
    port=3306,
    user='root',
    password='',
    database='school',
    charset='utf8'
)

  • 封装基于数据库连接池的常用数据库操作方法

# utils/helper_sql.py

import pymysql
from settings import POOL


class HelperSQL(object):
    def __init__(self, sql, parameter=[]):
        self.sql = sql
        self.parameter = parameter

# 查询多条数据
    def fetch_all(self):
        conn = POOL.connection()
        cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
        cursor.execute(self.sql, self.parameter)
        result = cursor.fetchall()
        cursor.close()
        conn.close()
        return result

# 查询一条数据
    def fetch_one(self):
        conn = POOL.connection()
        cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
        cursor.execute(self.sql, self.parameter)
        result = cursor.fetchone()
        cursor.close()
        conn.close()
        return result

 # 添加数据
    def insert(self):
        conn = POOL.connection()
        cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
        row = cursor.execute(self.sql, self.parameter)
        conn.commit()
        cursor.close()
        conn.close()
        return row

  • 调用封装好的基于数据库连接池的常用数据库操作方法

# index.py

from utils.helper_sql import HelperSQL

# 查询多条或一条数据
sql = 'select * from student where age=%s'
select_sql_obj = HelperSQL(sql, [18])
print(select_sql_obj.fetch_all())  # [{'name': 'Kevin', 'age': 18, 'id': 1}, {'name': 'Yeung', 'age': 18, 'id': 2}]
print(select_sql_obj.fetch_one())  # {'name': 'Kevin', 'age': 18, 'id': 1}

# 添加数据
sql = 'insert into student(name, age) values(%s, %s)'
insert_sql_obj = HelperSQL(sql, ['AGA', 25])
insert_sql_obj.insert()